#include "skled.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <pthread.h>
#include <linux/spi/spidev.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>

#define skled_ret_val_if_fail(conditional, ret) \
    if (!(conditional)) { \
        printf("check %s failed\n", #conditional); \
        return ret; \
    }

#define skled_ret_if_fail(conditional) \
    if (!(conditional)) { \
        printf("check %s failed\n", #conditional); \
        return; \
    }

struct skled {
    int spi_fd;
    enum skled_model model;
    uint8_t led_numbers;
    uint8_t tx_buf_dirty:1;
    uint8_t *spi_tx_buf;
    uint32_t spi_tx_buf_len;
    pthread_mutex_t lock;
};

/**
 * order should be same with skled_model define
 * XXX: for reset signal the zero/one
 * last bit is alway zero, so just sleep the
 * required time seems fine. for now only
 * support SK9816, other model maybe need
 * add other configs, such as bit_order
 */
static struct skled_proto_configs {
    uint32_t spi_clk_hz;
    uint8_t spi_bits_per_word;
    uint8_t sk_encoder_bits;
    uint8_t sk_rgb_bits;
    uint8_t sk_current_gain_bits;
    uint8_t sk_zero_encode;
    uint8_t sk_one_encode;
    float reset_time_us;
} skconfigs[] = {
    {.spi_clk_hz = 3340*1000, .spi_bits_per_word = 8, .sk_encoder_bits = 4, .sk_rgb_bits = 24,
        .sk_current_gain_bits = 16, .sk_zero_encode = 0b1000, .sk_one_encode = 0b1110, .reset_time_us = 90}
};

static int skled_flush_txbuf(struct skled *sk);

int skled_create(struct skled **sk,
        enum skled_model model, uint8_t led_numbers,
        const char *spi_dev)
{
    skled_ret_val_if_fail(sk != NULL, -EINVAL);
    skled_ret_val_if_fail(spi_dev != NULL, -EINVAL);
    skled_ret_val_if_fail(model < SK_MODEL_LAST, -EINVAL);

    struct skled *skled_s = NULL;
    uint8_t spi_mode = 0;
    int ret = 0;
    struct skled_proto_configs *config = &skconfigs[model];

    skled_s = calloc(1, sizeof(struct skled));
    if (!skled_s) {
        printf("alloc skled fail\n");
        return -ENOMEM;
    }

    //config spi
    skled_s->spi_fd = open(spi_dev, O_RDWR);
    if (skled_s->spi_fd < 0) {
        free(skled_s);
        return -ENODEV;
    }

    ret = ioctl(skled_s->spi_fd, SPI_IOC_WR_MODE, &spi_mode);
    if (ret < 0) {
        close(skled_s->spi_fd);
        free(skled_s);
        return  -EINVAL;
    }

    ret = ioctl(skled_s->spi_fd, SPI_IOC_WR_BITS_PER_WORD, &config->spi_bits_per_word);
    if (ret < 0) {
        close(skled_s->spi_fd);
        free(skled_s);
        return -EINVAL;
    }

    ret = ioctl(skled_s->spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &config->spi_clk_hz);
    if (ret < 0) {
        close(skled_s->spi_fd);
        free(skled_s);
        return -EINVAL;
    }

    pthread_mutex_init(&skled_s->lock, NULL);

    uint32_t txbuf_bits = (config->sk_encoder_bits * config->sk_rgb_bits) * led_numbers + \
                          config->sk_current_gain_bits * config->sk_encoder_bits;
    skled_s->spi_tx_buf_len = (txbuf_bits % 8 != 0) ? (txbuf_bits + 8) / 8 : txbuf_bits / 8;

    skled_s->spi_tx_buf = calloc(1, skled_s->spi_tx_buf_len);
    if (!skled_s->spi_tx_buf) {
        close(skled_s->spi_fd);
        free(skled_s);
        return -ENOMEM;
    }

    skled_s->led_numbers = led_numbers;
    skled_s->model = model;
    *sk = skled_s;

    // print spi real config to check with config
    ret = ioctl(skled_s->spi_fd, SPI_IOC_RD_MODE, &spi_mode);
    if (ret < 0) {
        printf("SPI_IOC_RD_MODE fail\n");
        free(skled_s->spi_tx_buf);
        close(skled_s->spi_fd);
        free(skled_s);
        return -EINVAL;
    }

    uint8_t bpw = 0;
    ret = ioctl(skled_s->spi_fd, SPI_IOC_RD_BITS_PER_WORD, &bpw);
    if (ret < 0) {
        printf("SPI_IOC_RD_BITS_PER_WORD fail\n");
        free(skled_s->spi_tx_buf);
        close(skled_s->spi_fd);
        free(skled_s);
        return -EINVAL;
    }

    uint32_t speed = 0;
    ret = ioctl(skled_s->spi_fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
    if (ret < 0) {
        printf("SPI_IOC_RD_MAX_SPEED_HZ fail\n");
        free(skled_s->spi_tx_buf);
        close(skled_s->spi_fd);
        free(skled_s);
        return -EINVAL;
    }

    printf("spi mode: %d\n", spi_mode);
    printf("bits_per_word: %d\n", bpw);
    printf("max_speed: %dHZ (%dKHZ)\n", speed, speed/1000);
    return 0;
}

void skled_destroy(struct skled *sk)
{
    skled_ret_if_fail(sk != NULL);

    close(sk->spi_fd);
    if (sk->spi_tx_buf)
        free(sk->spi_tx_buf);
    pthread_mutex_destroy(&sk->lock);

    free(sk);
} 

static void hex_dump(const void *src, size_t length, size_t line_size,
		     char *prefix)
{
	int i = 0;
	const unsigned char *address = src;
	const unsigned char *line = address;
	unsigned char c;

	printf("%s | ", prefix);
	while (length-- > 0) {
		printf("%02X ", *address++);
		if (!(++i % line_size) || (length == 0 && i % line_size)) {
			if (length == 0) {
				while (i++ % line_size)
					printf("__ ");
			}
			printf(" | ");  /* right close */
			while (line < address) {
				c = *line++;
				printf("%c", (c < 33 || c == 255) ? 0x2E : c);
			}
			printf("\n");
			if (length > 0)
				printf("%s | ", prefix);
		}
	}
}

//spi default is MSB, for sk9816 one color is also MSB
//so convert high bit first 
//XXX:only support sk9816 spec, other chip need to check byte_order/bits_order
#define ACOLOR_BITS(rgb_bits) (rgb_bits / 3)
static uint8_t *get_led_txbuf_nolock(struct skled *sk, uint8_t led_index, enum skled_color color)
{
    uint32_t led_txbuf_offset = 0;
    uint32_t acolor_bytes = 0;
    uint8_t *txbuf = NULL;
    struct skled_proto_configs *config = &skconfigs[sk->model];

    led_txbuf_offset = ((config->sk_encoder_bits * config->sk_rgb_bits) / 8) * led_index;
    txbuf = sk->spi_tx_buf + led_txbuf_offset;
    acolor_bytes = (config->sk_encoder_bits * ACOLOR_BITS(config->sk_rgb_bits) / 8);

    //for sk9816 buffer order is "r8 g8 b8"
    txbuf += acolor_bytes * color;
    //clear buffer to zero
    bzero(txbuf, acolor_bytes);
    return txbuf;
}

#define AGAIN_BITS(gain_bits) (gain_bits / 4)
static uint8_t *get_led_gain_txbuf_nolock(struct skled *sk, enum skled_color color)
{
    struct skled_proto_configs *config = &skconfigs[sk->model];
    uint32_t txbuf_gain_offset = ((config->sk_encoder_bits * config->sk_rgb_bits) / 8) * sk->led_numbers;
    uint8_t *gain_buf = sk->spi_tx_buf + txbuf_gain_offset;
    uint32_t again_buf_bytes = (config->sk_encoder_bits * AGAIN_BITS(config->sk_current_gain_bits)) / 8;


    //for sk9816, order is "r4 g4 b4 4" 
    gain_buf += again_buf_bytes * color;
    bzero(sk->spi_tx_buf + txbuf_gain_offset, again_buf_bytes);
    return gain_buf;
}

static int set_led_color_value_nolock(struct skled *sk, uint8_t led_index, enum skled_color color, uint8_t value)
{
    uint8_t i = 0;
    uint8_t *txbuf = NULL;
    struct skled_proto_configs *config = &skconfigs[sk->model];
    uint8_t max_color_bit_index = ACOLOR_BITS(config->sk_rgb_bits) - 1;

    txbuf = get_led_txbuf_nolock(sk, led_index, color);
    if (txbuf) {
        for (i = max_color_bit_index; i >= 0; i--) {
            if ((value >> i) & 0x01) {
                *txbuf |= (i % 2 != 0) ? (config->sk_one_encode << config->sk_encoder_bits) : (config->sk_one_encode);
            } else {
                *txbuf |= (i % 2 != 0) ? (config->sk_zero_encode << config->sk_encoder_bits) : (config->sk_zero_encode);
            }

            if (i % 2 == 0) txbuf++;
        }
    }

    return txbuf ? 0 : -1;
}

static int set_led_color_value(struct skled *sk, uint8_t led_index, enum skled_color color, uint8_t value)
{
    int ret = 0;

    pthread_mutex_lock(&sk->lock);
    ret = set_led_color_value_nolock(sk, led_index, color, value);
    pthread_mutex_unlock(&sk->lock);

    return ret;
}

static int set_led_gain_nolock(struct skled *sk, enum skled_color color, uint8_t value)
{
    uint8_t *gain_buf = NULL;
    uint8_t i = 0;
    struct skled_proto_configs *config = &skconfigs[sk->model];
    uint8_t max_gain_bit_index = AGAIN_BITS(config->sk_current_gain_bits) - 1;

    gain_buf = get_led_gain_txbuf_nolock(sk, color);
    if (gain_buf) {
        for (i = max_gain_bit_index; i >= 0; i--) {
            if ((value >> i) & 0x01) {
                *gain_buf |= (i % 2 != 0) ? (config->sk_one_encode << config->sk_encoder_bits) : (config->sk_one_encode);
            } else {
                *gain_buf |= (i % 2 != 0) ? (config->sk_zero_encode << config->sk_encoder_bits) : (config->sk_zero_encode);
            }

            if (i % 2 == 0) gain_buf++;
        }  
    }
 
    return gain_buf ? 0 : -1;
}

static int set_led_gain(struct skled *sk, enum skled_color color, uint8_t value)
{
    int ret = 0;

    pthread_mutex_lock(&sk->lock);
    ret = set_led_gain_nolock(sk, color, value);
    pthread_mutex_unlock(&sk->lock);

    return ret;
}

int skled_set_red(struct skled *sk, uint8_t led_index, uint8_t red)
{
    skled_ret_val_if_fail(sk != NULL, -EINVAL);
    return set_led_color_value(sk, led_index, SKLED_RED, red);
}

int skled_set_green(struct skled *sk, uint8_t led_index, uint8_t green)
{
    skled_ret_val_if_fail(sk != NULL, -EINVAL);
    return set_led_color_value(sk, led_index, SKLED_GREEN, green);
}

int skled_set_blue(struct skled *sk, uint8_t led_index, uint8_t blue)
{
    skled_ret_val_if_fail(sk != NULL, -EINVAL);
    return set_led_color_value(sk, led_index, SKLED_BLUE, blue);
}

int skled_set_rgb(struct skled *sk, uint8_t led_index, uint8_t red, uint8_t green, uint8_t blue)
{
    skled_ret_val_if_fail(sk != NULL, -EINVAL);

    skled_set_red(sk, led_index, red);
    skled_set_green(sk, led_index, green);
    skled_set_blue(sk, led_index, blue);

    //XXX:flush buffer, not very efficient
    return skled_flush_txbuf(sk);
}

int skled_set_red_gain(struct skled *sk, uint8_t rgain)
{
    skled_ret_val_if_fail(sk != NULL, -EINVAL);

    return set_led_gain(sk, SKLED_RED, rgain);
}

int skled_set_green_gain(struct skled *sk, uint8_t ggain)
{
    skled_ret_val_if_fail(sk != NULL, -EINVAL);

    return set_led_gain(sk, SKLED_GREEN, ggain);
}

int skled_set_blue_gain(struct skled *sk, uint8_t bgain)
{
    skled_ret_val_if_fail(sk != NULL, -EINVAL);

    return set_led_gain(sk, SKLED_BLUE, bgain);
}

int skled_set_rgb_gain(struct skled *sk, uint8_t rgain, uint8_t ggain, uint8_t bgain)
{
    skled_ret_val_if_fail(sk != NULL, -EINVAL);

    skled_set_red_gain(sk, rgain);
    skled_set_green_gain(sk, ggain);
    skled_set_blue_gain(sk, bgain);

    return skled_flush_txbuf(sk);
}

static int skled_flush_txbuf(struct skled *sk)
{
    skled_ret_val_if_fail(sk != NULL, -EINVAL);

    int ret = 0;
    struct skled_proto_configs *config = &skconfigs[sk->model];

    pthread_mutex_lock(&sk->lock);
    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)sk->spi_tx_buf,
        .rx_buf = (unsigned long)NULL,
        .len = sk->spi_tx_buf_len,
        .delay_usecs = 0,
        .speed_hz = config->spi_clk_hz,
        .bits_per_word = config->spi_bits_per_word,
    };

    ret = ioctl(sk->spi_fd, SPI_IOC_MESSAGE(1), &tr);
    if (ret < 1) {
        pthread_mutex_unlock(&sk->lock);
        printf("SPI_IOC_MESSAGE fail: %d\n", ret);
        return ret;
    }

    pthread_mutex_unlock(&sk->lock);

    usleep(config->reset_time_us);
    return 0;
}
